/*
 *	$Id: vizobj.cpp 162 2009-06-19 20:31:20Z wabsie $
 */

#include "vizobj.h"	// Hvis andre #include's i denne fil end denne, så slet dem


int			vizobj::cnt = 0;
const char*	vizobj::felter[] = {"Electric field, E",
								"Electric potential, V",
								"Magnetic field, B",
								"Magnetic potential, A"};
const char*	vizobj::radioItems[] = {"2D", "3D", NULL};

vizobj::vizobj(const char* _navn) :
	/* Initializer list */
	closed(false),			// Vinduet er ikke lukket (vi lige har åbnet det)

	cslice('z'), nslice(0),	// Default slice: z = 0
	styrkeDbl(0),			// En eller anden default styrke
	dimInt(0),				// Vælg 2D per default (index 0=2D, index 1=3D)
	autoBool(1),			// Automatisk farve
	objectSelection(-1),	// Intet objekt valgt
	fieldSelection(intet)	// Intet felt valgt på forhånd
{
	/*
	 * Opretter et nyt VIZ-objekt:
	 *  Tegner et nyt vindue og et tomt (ingen emobjekter) plot.
	 */

	// Initialisér øvrige variable, som er umulige i initializer-listen
	strncpy(this->navn, _navn, sizeof(this->navn));
	sliceStr[0] = '\0';
	for (int i = 0; i < 3; i++) {
		rotValue[i] = 0;			// Ingen rotering til start

        axis[i].min = -1;			// Alle akser spænder fra -1 til 1
        axis[i].max = 1;			// ...
		axis[i].str[0] = '\0';

		offDbl[i] = 0;				// Nulstil em-objekt offset-numericals
		retDbl[i] = 0;				// Nulstil em-objekt orientering-numericals

		slice.planes[i] = axis[i].min;
	}

	{
		vem.setMediumRelative(1, 1);	// Free space

//      Elektrisk "dipol" af line charges
//		vem.addObj(emobj::lcharge,  1e-9, vektor<3>(0, 1, 0), vektor<3>(0, 0, 1));
//		vem.addObj(emobj::lcharge, -1e-9, vektor<3>(0,-1, 0), vektor<3>(0, 0, 1));
	}

	this->draw();			// Tegn VIZ-vinduet

	AG_Event evnt;			// Opret en fake event, for at lade callbacks tegne det sidste
	evnt.argv[1].p = (void*)this;
	fieldDimension(&evnt);	// 2D er valgt, så skjul 3D rotation-sliders
	fieldSlice(&evnt);		// Opdatér slice-textbox
	objectSelected(&evnt);	// Ingen objekter valgt, så disable offset og orientation
	for (int k = 0; k < 3; k++) {
		evnt.argv[2].i = k;
		editAxis(&evnt);	// Opdatér akse-textbox'e
	}

	farveMinDbl = farveMaxDbl = -INF;

	return;
}

vizobj::~vizobj()
{
//	printf("Vizvindue %i destructor kaldt\n", this->id);

	return;
}

bool vizobj::isWinEqual(AG_Window *arg)
{
	return (this->vizWin == arg); // Sammenlign pointer adresser
}

void vizobj::giveFocus()
{
	AG_WindowUnminimize(this->vizWin);
	AG_WindowFocus(this->vizWin);

	return;
}

char* vizobj::getName()
{
	return this->navn;
}

void vizobj::fieldSelected(AG_Event *event)
{	/* Agar call convention: ucombo-selected (AG_TlistItem *item) */
	vizobj *This = (vizobj*)AG_PTR(1);
	AG_TlistItem *item = (AG_TlistItem*)AG_PTR(2);

	This->fieldSelection = intet;	// Som udgangspunkt er intet objekt valgt

	// Find vem-indexet for valgt emobjekt item
	for(int i = 1;; i++) {
		AG_TlistItem *foo = AG_TlistFindByIndex(This->fieldUcom->list, i);
		if(foo == NULL)
			break;	// Ikke flere items tilbage
		if(foo == item) {
			This->fieldSelection = (fS)(i-1); // Agar er 1-baseret, vi er 0-baseret
			break;	// Succes!
		}
	}

	// Referancepotentialet må kun ændres når vi viser et potentialefelt
	switch (This->fieldSelection) {
		case V:
		case A:
			for (int i = 0; i < 3; i++)
				AG_NumericalSetWriteable(This->zeropotNum[i], true); // Aktivér
			break;

		case E:
		case B:
			for (int i = 0; i < 3; i++)
				AG_NumericalSetWriteable(This->zeropotNum[i], false); // Deaktivér
			break;
	}

//	printf("fieldSelected: Du valgte %s, %i\n", item->text, This->fieldSelection);

	This->plotwrapper();
	return;
}

void vizobj::fieldDimension(AG_Event *event)
{	/* Agar call convention: radio-changed (int index) */
	vizobj *This = (vizobj*)AG_PTR(1);

//	printf("fieldDimension: Du valgte %i\n", This->dimInt);

	if (string(radioItems[This->dimInt]) == "3D") {
		static const char* axLbls[3] =	{"x-axis:      ",	// Underlig spacing
										"y-axis:      ",	// modvirker quirks i
										"z-axis:      "};	// Agar's FreeType render
		static const Uint32 axCols[3] = {colorRed, colorGreen, colorBlue};

		AG_WidgetEnable(This->axis[2].Text);	// I 3D får vi brug for en 3. akse

		for (int i = 0; i < 3; i++)
		{
			AG_TextboxSetLabel(This->axis[i].Text, axLbls[i]);
			setPixmapColor(This->axis[i].Pix, axCols[i]);
			
			// Agar bug: AG_WidgetDisable() virker ikke på AG_Slider!
			AG_WidgetShow(This->rotSlider[i]);	// Kun i 3D skal rotationsliders vises
			AG_WidgetShow(This->rotLbl[i]);		// og deres labels for synsskyld
		}
	} else {
		static const char* axLbls[3] = {"1st axis:  ", "2nd axis: ", "3rd axis:  "};

		AG_WidgetDisable(This->axis[2].Text);	// Ingen 3. akse i 2D

		for (int i = 0; i < 3; i++)
		{
			AG_TextboxSetLabel(This->axis[i].Text, axLbls[i]);
			setPixmapColor(This->axis[i].Pix, colorAlmostBlack);

			// Agar bug: AG_WidgetDisable() virker ikke på AG_Slider!
			AG_WidgetHide(This->rotSlider[i]);	// Skjul rotationsliders i 2D
			AG_WidgetHide(This->rotLbl[i]);		// og deres labels for synsskyld
		}
	}

	This->plotwrapper();
	return;
}

void vizobj::editAxis(AG_Event *event)
{	/* Agar call convention: textbox-return (void) */
	vizobj *This = (vizobj*)AG_PTR(1);
	int i = AG_INT(2);

	const int ssize = sizeof(This->axis[i].str);

	// Fjern leading og trailing whitespace
	strncpy(This->axis[i].str, parser::trim(This->axis[i].str).c_str(), ssize);

	// Parse min/max ud
	sscanf(This->axis[i].str, "%f : %f", &(This->axis[i].min), &(This->axis[i].max));

	// Skriv tilbage til tekstboksen hvordan vi opfattede inputtet
	snprintf(This->axis[i].str, ssize, "%.3f : %.3f", This->axis[i].min, This->axis[i].max);

	This->plotwrapper();
	return;
}

void vizobj::fieldSlice(AG_Event *event)
{	/* Agar call convention: textbox-return (void) */
	vizobj *This = (vizobj*)AG_PTR(1);

	const int ssize = sizeof(This->sliceStr);

	// Fjern leading og trailing whitespace
	strncpy(This->sliceStr, parser::trim(This->sliceStr).c_str(), ssize);

	// Find slice-plan
	sscanf(This->sliceStr, "%c = %f", &(This->cslice), &(This->nslice));

	// Sanity check
	switch (This->cslice) {
		case 'x': case 'X': This->cslice = 'x'; break;
		case 'y': case 'Y': This->cslice = 'y'; break;
		case 'z': case 'Z': This->cslice = 'z'; break;
		default: This->cslice = 'z'; // Vælg et lovligt snitplan
	}

	// Skriv tilbage til tekstboksen hvordan vi opfattede inputtet
	snprintf(This->sliceStr, ssize, "%c = %f", This->cslice, This->nslice);

	This->slice.current = This->cslice - 'x';
	This->slice.planes[This->slice.current] = This->nslice;

	This->plotwrapper();
	return;
}

void vizobj::objectSelected(AG_Event *event)
{	/* Agar call convention: ucombo-selected (AG_TlistItem *item) */
	/*
	 * Brugeren har valgt et em-objekt han vil manipulere; opdatér manipulator-
	 * controls (styrke, translatation, evt. orientering) tilsvarende.
	 */

	vizobj *This = (vizobj*)AG_PTR(1);
	AG_TlistItem *item = (AG_TlistItem*)AG_PTR(2);

	This->objectSelection = -1;	// Som udgangspunkt er intet objekt valgt

	// Find vem-indexet for valgt emobjekt item
	for(int i = 1;; i++) {
		AG_TlistItem *foo = AG_TlistFindByIndex(This->objectUcom->list, i);
		if(foo == NULL)
			break; // Ikke flere items tilbage
		if(foo == item) {
			This->objectSelection = i - 1; // Agar er 1-baseret, vi er 0-baseret
			break; // Succes!
		}
	}

	if (This->objectSelection == -1) {
//		printf("Intet objekt valgt\n");
		This->disallowObjManip();
		return;
	}

//	printf("objectSelected: Du valgte '%s', index = %i\n", item->text, This->objectSelection);

	// Et gyldigt objekt er valgt; tillad brugeren at manipulere det
	This->allowObjManip();

	// Bruger valgte ny objekt: Opdatér styrke-label tilsvarende
    This->styrkeDbl = This->vem[This->objectSelection]->styrke;
	switch(This->vem[This->objectSelection]->mintype)
	{
		case emobj::pcharge :
			AG_TextboxSetLabel(This->styrkeNum->input, "Q [C]: ");
			break;
		case emobj::lcharge :
			AG_TextboxSetLabel(This->styrkeNum->input, "rho_l [C/m]: ");
			break;
		case emobj::lcurrent :
			AG_TextboxSetLabel(This->styrkeNum->input, "I [A]: ");
			break;
		default:
			AG_TextboxSetLabel(This->styrkeNum->input, "Unknown: ");
			break;
	}

	// Og vis objektets nuværende offset og orientering
	for(int i = 0; i < 3; i++) {
		This->offDbl[i] = This->vem[This->objectSelection]->off.data[i][0];
		This->retDbl[i] = This->vem[This->objectSelection]->getOrient().data[i][0];
	}

	// Hvis point charge, så disable orientering-numerical's
	switch(This->vem[This->objectSelection]->mintype)
	{
		case emobj::pcharge :
			for (int i = 0; i < 3; i++)
				AG_NumericalSetWriteable(This->retNum[i], false); // Deaktivér
			break;

		default:
			for (int i = 0; i < 3; i++)
				AG_NumericalSetWriteable(This->retNum[i], true); // Aktivér
			break;
	}

	return;
}

void vizobj::styrkeEdit(AG_Event *event)
{	/* Agar call convention: numerical-changed (void) */
	vizobj *This = (vizobj*)AG_PTR(1);

	This->vem[This->objectSelection]->styrke = This->styrkeDbl;

	This->plotwrapper();
	return;
}

void vizobj::ucomboUpdate(AG_Event *event)
{
	vizobj *This = (vizobj*)AG_PTR(1);

    AG_TlistClear(This->objectUcom->list);	// Ryd listen...

	// ... og print en ny ud over alle emobjekterne
    for (int i = 0; i < This->vem.size(); i++) {
        char buffer[BUFSIZ];
        snprintf(buffer, sizeof(buffer), "%d %s", i+1, This->vem[i]->getInfo().c_str());
        AG_TlistAdd(This->objectUcom->list, NULL, buffer);
    }

	return;
}

void vizobj::offsetUpdate(AG_Event *event)
{
	vizobj *This = (vizobj*)AG_PTR(1);
	int index = AG_INT(2);

	This->vem[This->objectSelection]->off.data[index][0] = This->offDbl[index];

	This->plotwrapper();
	return;
}

void vizobj::orientUpdate(AG_Event* event)
{
	vizobj *This = (vizobj*)AG_PTR(1);
	int index = AG_INT(2);

	// Opdatér retningsvektoren for valgt objekt
	vektor<3> orient = This->vem[This->objectSelection]->getOrient();
	orient.data[index][0] = This->retDbl[index];
	This->vem[This->objectSelection]->setOrient(orient);

	This->plotwrapper();
	return;
}

void vizobj::close()
{	
	/* Lukker vinduet ned */
	AG_ViewDetach(this->vizWin);
	this->closed = true;
//	printf("Viz vindue %s lukket\n", this->navn);

	return;
}

void vizobj::winClosed(AG_Event *event)
{	/* Agar call convention: window-user-resize (int w, int h) */
	/* Callback når bruger klikker på X'et i vinduets titellinje */
	vizobj *This = (vizobj*)AG_PTR(1);

	This->close();	// Luk ned
	return;
}

void vizobj::genericPlotUpdate(AG_Event *event)
{
	/* Generel callback når plottet kun skal opdateres */
	vizobj *This = (vizobj*)AG_PTR(1);

	This->plotwrapper();
	return;
}

void vizobj::plotwrapper()
{
	/*
	 * Tjekker om det valgte felt kan plottes med de nuværende emobjekter.
	 * Fx kan A-feltet ikke vises hvis der er kun er tilføjet en point charge.
	 * Giv fejl medd. hvis dette er tilfældet, ellers plot feltet:
	 * Opdatér AG_Pixmap plottet: Kald plot metoden fra plotter-klassen; denne
	 * returnerer en AG_Surface. I denne funktion skal vi således blot opdatere
	 * AG_Pixmap'et med AG_Surface'en
	 */

	// (De)aktivér adaptivfarve
	if (this->autoBool == true) {
		AG_NumericalSetWriteable(this->farveMinNum, false);
		AG_NumericalSetWriteable(this->farveMaxNum, false);
	} else {
		AG_NumericalSetWriteable(this->farveMinNum, true);
		AG_NumericalSetWriteable(this->farveMaxNum, true);
	}

	// Takket være AG_PixmapReplaceCurrentSurface tilpasses pixmap'et automatisk
	// til surface størrelsen. Vi lader derfor plotPix udfylde hele plotBox'en
	const skalar width = plotBox->wid.w - 2*(plotBox->padding+1);
	const skalar heigth = plotBox->wid.h - 2*(plotBox->padding+1);

	skalar xmin = axis[0].min;	skalar xmax = axis[0].max;
	skalar ymin = axis[1].min;	skalar ymax = axis[1].max;

	coord<3> centrum;
	skalar theta = 30 * (pi/180); // 2*theta = 60 grader field of view er passende
	skalar f, W, radius;

	if (string(radioItems[dimInt]) == "3D")
	{
		// Hjørnerne for vores 3D box
		coord<3> corners[8] = {
			coord<3>(axis[0].min, axis[1].min, axis[2].min),
			coord<3>(axis[0].min, axis[1].min, axis[2].max),
			coord<3>(axis[0].min, axis[1].max, axis[2].min),
			coord<3>(axis[0].min, axis[1].max, axis[2].max),
			coord<3>(axis[0].max, axis[1].min, axis[2].min),
			coord<3>(axis[0].max, axis[1].min, axis[2].max),
			coord<3>(axis[0].max, axis[1].max, axis[2].min),
			coord<3>(axis[0].max, axis[1].max, axis[2].max)
		};

		// Koordinater til boksens centrum
		centrum.x = (axis[0].min + axis[0].max)/2;
		centrum.y = (axis[1].min + axis[1].max)/2;
		centrum.z = (axis[2].min + axis[2].max)/2;

		// Find radius for den sfære som indeholder boksen
		radius = 0;
		for (int i = 0; i < 8; i++) {
			coord<3> tmp = corners[i] - centrum;
			if (tmp.norm() > radius)
				radius = tmp.norm();
		}

		W = 1*radius;	// Viewplanet er givet ved ligningen: z = w.
						// Viewplanet kan flyttes som ønsket, blot f > w.

		f = radius / sin(theta); // Fokus
		assert(radius > 0);
		assert(f > 0);
		assert(W > 0);
		assert(f > W);

		// Find 2D projektionplanets bredde og højde
		skalar h = radius/cos(theta) - radius*tan(theta); // w = 1*r nominelt
		xmin = ymin = -h;
		xmax = ymax = h;
	}

	skalar xrange = xmax - xmin;
	skalar yrange = ymax - ymin;
	
	// Sørg for 1:1 aspektforhold (ens afstand mellem tickmarks i 2D)
	if (width/heigth >= xrange/yrange) {
		xrange = yrange * (width/heigth);

		skalar xsnit = (xmin + xmax)/2;
		xmin = xsnit - xrange/2;
		xmax = xsnit + xrange/2;
	} else {
		yrange = xrange * (heigth/width);

		skalar ysnit = (ymin + ymax)/2;
		ymin = ysnit - yrange/2;
		ymax = ysnit + yrange/2;
	}

	view parameters; {
		parameters.adaptiveColor = this->autoBool;

		parameters.plotWidth = int(width);		
		parameters.plotHeigth = int(heigth);
		parameters.intensityWidth = farvePix->wid.w;
		parameters.intensityHeigth = farvePix->wid.h;

		parameters.cslice = cslice;		// Sliceplanet
		parameters.nslice = nslice;		// ...

		parameters.firstAxMin = xmin;	// Akseinterval for skærmplanet.
        parameters.firstAxMax = xmax;	// I 2d er skærmplanet blot sliceplanet,
        parameters.secondAxMin = ymin;	// i 3d er det blot projektionsplanet.
        parameters.secondAxMax = ymax;	// ...

		// 3D akseinterval leveres separat
		parameters.view3d.x.min = axis[0].min;	parameters.view3d.x.max = axis[0].max;
		parameters.view3d.y.min = axis[1].min;	parameters.view3d.y.max = axis[1].max;
		parameters.view3d.z.min = axis[2].min;	parameters.view3d.z.max = axis[2].max;

		parameters.view3d.alpha = rotValue[0];	// Rotation
		parameters.view3d.beta = rotValue[1];	// ...
		parameters.view3d.gamma = rotValue[2];
	};

	Medium pruned;
	plotter plot(parameters);

	if (this->autoBool == false) {
		plot.farve.min = this->farveMinDbl;
		plot.farve.max = this->farveMaxDbl;
	}

	plot.slice = this->slice;

	switch (this->fieldSelection) {
		case E: pruned.mainField = Field::primary;		break;
		case V:	pruned.mainField = Field::potential;	break;
		case B: pruned.mainField = Field::primary;		break;
		case A:	pruned.mainField = Field::potential;	break;
	}

	// Beskær feltkonfigurationen til de objekter der medfører det valgt felt
	switch (this->fieldSelection) {
		case E: // For E og V felter vil vi kun tage punkt-
		case V:	// og linieladninger med i beregningerne.
			for (int i = 0; i < vem.size(); i++) {
				switch (vem[i]->mintype) {
					case emobj::pcharge :
					case emobj::lcharge : pruned.push_back(vem[i]);
				}
			}
			break;

		case B:	// For B og A felter vil vi kun tage
		case A:	// liniestrømme med i beregningerne.
			for (int i = 0; i < vem.size(); i++) {
				switch (vem[i]->mintype) {
					case emobj::lcurrent : pruned.push_back(vem[i]);
				}
			}
			break;
	}

	// Plot...
	if (vem.empty()) {
		plot.msgNoObjects();			// Ingen objekter tilføjet; oplys brugeren
	} else if (pruned.empty()) {
		plot.msgNoSuitableObjects();	// Ingen passende objekter tilføjet
	} else if (string(radioItems[dimInt]) == "2D") {
		plot.tegn2d(pruned);
	} else {
		plot.tegn3d(pruned, f, W, centrum);
	}

	if (this->autoBool == true) {
		this->farveMinDbl = plot.farve.min;
		this->farveMaxDbl = plot.farve.max;
	}

	// Vis det nye plot på skærmen (dvs. kopiér surface over til pixmap'et)
	AG_PixmapReplaceCurrentSurface(plotPix, plot.getPlot());
    AG_PixmapReplaceCurrentSurface(farvePix, plot.getIntensity());

	return;
}

void vizobj::allowObjManip()
{
	AG_NumericalSetWriteable(this->styrkeNum, true);
	for(int i = 0; i < 3; i++) {
		AG_NumericalSetWriteable(this->offNum[i], true);
		AG_NumericalSetWriteable(this->retNum[i], true);
	}

	return;
}

void vizobj::disallowObjManip()
{
	AG_NumericalSetWriteable(this->styrkeNum, false);
	for(int i = 0; i < 3; i++) {
		AG_NumericalSetWriteable(this->offNum[i], false);
		AG_NumericalSetWriteable(this->retNum[i], false);
	}

	return;
}

void vizobj::removeObj(int index)
{
	assert(index >= 0);
	assert(!vem.empty());

	vem.erase(vem.begin() + index);

	if (vem.empty())
		disallowObjManip();

	return;
}

void vizobj::draw()
{
	/* 
	 * Opret og tegn et nyt VIZ-vindue (Visual Inspection Zone).
	 */

	vizWin = AG_WindowNew(AG_WINDOW_FOCUSONATTACH | AG_WINDOW_NOMAXIMIZE);
	AG_WindowSetCaption(vizWin, navn);
	AG_SetEvent(vizWin, "window-user-resize", genericPlotUpdate, "%p", this);
	AG_SetEvent(vizWin, "window-close", winClosed, "%p", this);

	// Opret en horizontal skillevæg mellem widget-controls og plottet
	AG_Pane	*pane = AG_PaneNew(vizWin, AG_PANE_HORIZ, AG_PANE_EXPAND);
	plotBox = pane->div[0];		// Venstre halvdel, med AG_Pixmap plottet i
	controlsBox = pane->div[1];	// Højre halvdel; heri er vores widget-controls

    AG_Notebook *nb = AG_NotebookNew(controlsBox, AG_NOTEBOOK_EXPAND);


	AG_NotebookTab *viewTab;
	viewTab = AG_NotebookAddTab(nb, "View options", AG_BOX_VERT);
	AG_Box *viewBox = AG_BoxNewVert(viewTab, AG_BOX_EXPAND | AG_BOX_FRAME);
	{
		AG_Box *tmpBox;

		// Sæt en label og en dropdown list ved siden af hinanden
		tmpBox = AG_BoxNewHoriz(viewBox, AG_BOX_HFILL);
		{
			AG_LabelNew(tmpBox, 0, "View field: ");

			// Felt-selektor: En dropdown list med de forskellige felter
			fieldUcom = AG_UComboNew(tmpBox, AG_UCOMBO_EXPAND);
			const int size = sizeof(felter)/sizeof(*felter);	// Antal felter
			for (int j = 0; j < size; j++)
				AG_TlistAdd(fieldUcom->list, NULL, felter[j]);	// Tilføj felterne
			AG_SetEvent(fieldUcom, "ucombo-selected", fieldSelected, "%p", this);
			AG_UComboSizeHint(fieldUcom, "Magnetic potential, XX", 4);
		}

		tmpBox = AG_BoxNewHoriz(viewBox, AG_BOX_HFILL);
		{
			AG_LabelNew(tmpBox, 0, "Dimension: ");

			AG_Radio *D2D3radio;
			D2D3radio = AG_RadioNew(tmpBox, AG_RADIO_HFILL, radioItems);
			AG_WidgetBindInt(D2D3radio, "value", &dimInt);
			AG_SetEvent(D2D3radio, "radio-changed", fieldDimension, "%p", this);
		}

		AG_LabelNew(viewBox, 0, "Zero potential at point [x,y,z]: ");
		tmpBox = AG_BoxNewHoriz(viewBox, AG_BOX_HFILL | AG_BOX_HOMOGENOUS);
		AG_BoxSetPadding(tmpBox, 0);
		AG_BoxSetSpacing(tmpBox, 0);
		{
			for (int i = 0; i < 3; i++) {
				zeropotNum[i] = AG_NumericalNew(tmpBox, 0, NULL, "");

				AG_NumericalSetIncrement(zeropotNum[i], 0.1);
				AG_NumericalSizeHint(zeropotNum[i], "-00.00");
				AG_WidgetBindDouble(zeropotNum[i], "value", &vem.nul_potentiale.data[i][0]);

				AG_SetEvent(zeropotNum[i], "numerical-changed", genericPlotUpdate, "%p", this);
			}
		}

		{	// Sæt slice tekstboksen op
			AG_Textbox *tbox = AG_TextboxNew(viewBox, 0, "Slice plane: ");
			AG_TextboxBindASCII(tbox, sliceStr, sizeof(sliceStr));
			AG_SetEvent(tbox, "textbox-return", fieldSlice, "%p", this);
		}

		// Sæt tekstbokse op til ændring af view range
		for (int i = 0; i < 3; i++)
		{
			tmpBox = AG_BoxNewHoriz(viewBox, AG_BOX_HFILL);
			AG_BoxSetPadding(tmpBox, 0);
			AG_BoxSetSpacing(tmpBox, 0);
			{
				axis[i].Pix = AG_PixmapNew(tmpBox, 0, 1, agTextFontHeight);
				setPixmapColor(axis[i].Pix, colorRed);

				axis[i].Text = AG_TextboxNew(tmpBox, 0, "Xnd axis: ");
				AG_TextboxBindASCII(axis[i].Text, axis[i].str, sizeof(axis[i].str));
				AG_SetEvent(axis[i].Text, "textbox-return", editAxis, "%p,%i", this, i);
			}
		}

		rotLbl[0] = AG_LabelNewPolled(viewBox, AG_LABEL_HFILL, "Rotation [alpha = %d]: ", &rotValue[0]);
		rotSlider[0] = AG_SliderNewIntR(viewBox, AG_SLIDER_HORIZ, AG_SLIDER_HFILL, &rotValue[0], -90, 90);
		{
			AG_SetEvent(rotSlider[0], "slider-changed", genericPlotUpdate, "%p", this);
		}

		rotLbl[1] = AG_LabelNewPolled(viewBox, AG_LABEL_HFILL, "Rotation [beta = %d]: ", &rotValue[1]);
		rotSlider[1] = AG_SliderNewIntR(viewBox, AG_SLIDER_HORIZ, AG_SLIDER_HFILL, &rotValue[1], -180, 180);
		{
			AG_SetEvent(rotSlider[1], "slider-changed", genericPlotUpdate, "%p", this);
		}

		rotLbl[2] = AG_LabelNewPolled(viewBox, AG_LABEL_HFILL, "Rotation [gamma = %d]: ", &rotValue[2]);
		rotSlider[2] = AG_SliderNewIntR(viewBox, AG_SLIDER_HORIZ, AG_SLIDER_HFILL, &rotValue[2], -180, 180);
		{
			AG_SetEvent(rotSlider[2], "slider-changed", genericPlotUpdate, "%p", this);
		}
	}


	AG_NotebookTab *manipTab;
	manipTab = AG_NotebookAddTab(nb, "Field configuration", AG_BOX_VERT);
	AG_Box *manipBox = AG_BoxNewVert(manipTab, AG_BOX_EXPAND | AG_BOX_FRAME);
	{
		AG_Box *tmpBox;

		// Sæt en label og en dropdown list ved siden af hinanden
		tmpBox = AG_BoxNewHoriz(manipBox, AG_BOX_HFILL);
		{
			// Bed brugeren om at vælge det em-ombjekt som skal manipuleres
			AG_LabelNew(tmpBox, 0, "Edit object: ");

			// Object-selektor: En dropdown list med tilføjede em-objekter.
			// ucomboUpdate kaldes når dropdown listen vises, og opretter elementerne deri
			// objectSelected kaldes når bruger har valgt et element fra listen
            objectUcom = AG_UComboNewPolled(tmpBox, AG_UCOMBO_EXPAND, ucomboUpdate, "%p", this);
			AG_UComboSizeHint(objectUcom, 
				"0 LCurrent(0.00E-0, [0.00E00;0.00E00;0.00E00], [0.00E00;0.00E00;0.00E00])", 5);
			AG_SetEvent(objectUcom, "ucombo-selected", objectSelected, "%p", this);
		}

		styrkeNum = AG_NumericalNew(manipBox, AG_NUMERICAL_HFILL, NULL, "Unknown unit: ");
		{
			AG_NumericalSetIncrement(styrkeNum, 1e-6);
			AG_NumericalSetPrecision(styrkeNum, "g", 6);
			AG_WidgetBindDouble(styrkeNum, "value", &styrkeDbl);
			AG_SetEvent(styrkeNum, "numerical-changed", styrkeEdit, "%p", this);
		}

		AG_LabelNew(manipBox, 0, "Location [x,y,z]: ");
        tmpBox = AG_BoxNewHoriz(manipBox, AG_BOX_HFILL | AG_BOX_HOMOGENOUS);
		AG_BoxSetPadding(tmpBox, 0);
		AG_BoxSetSpacing(tmpBox, 0);
		{
			for (int i = 0; i < 3; i++) {
				offNum[i] = AG_NumericalNew(tmpBox, 0, NULL, "");

				AG_NumericalSetIncrement(offNum[i], 0.1);
				AG_NumericalSizeHint(offNum[i], "-00.00");
				AG_NumericalSetPrecision(offNum[i], "g", 2);
				AG_WidgetBindDouble(offNum[i], "value", &offDbl[i]);
				AG_SetEvent(offNum[i], "numerical-changed", offsetUpdate, "%p,%i",this, i);
			}
		}

        AG_LabelNew(manipBox, 0, "Orientation [x,y,z]: ");
        tmpBox = AG_BoxNewHoriz(manipBox, AG_BOX_HFILL | AG_BOX_HOMOGENOUS);
		AG_BoxSetPadding(tmpBox, 0);
		AG_BoxSetSpacing(tmpBox, 0);
		{
			for (int i = 0; i < 3; i++) {
				retNum[i] = AG_NumericalNew(tmpBox, 0, NULL, "");

				AG_NumericalSetIncrement(retNum[i], 0.1);
				AG_NumericalSizeHint(retNum[i], "-00.00");
				AG_WidgetBindDouble(retNum[i], "value", &retDbl[i]);
				AG_SetEvent(retNum[i], "numerical-changed", orientUpdate, "%p,%i",this, i);
			}
		}
	}

	AG_Box *tmpBox;
	tmpBox = AG_BoxNewHoriz(vizWin, AG_BOX_HFILL);
	AG_BoxSetPadding(tmpBox, 0);
	AG_BoxSetSpacing(tmpBox, 0);
	{
		AG_Button *autoBtn;
		autoBtn = AG_ButtonNew(tmpBox, AG_BUTTON_STICKY, "Auto");
		AG_WidgetBindBool(autoBtn, "state", &autoBool);
		AG_SetEvent(autoBtn, "button-pushed", genericPlotUpdate, "%p", this);

		farveMinNum = AG_NumericalNew(tmpBox, AG_NUMERICAL_NO_HFILL, NULL, "");
		AG_WidgetBindDouble(farveMinNum, "value", &farveMinDbl);
		AG_SetEvent(farveMinNum, "numerical-changed", genericPlotUpdate, "%p", this);

		farvePix = AG_PixmapNew(tmpBox, AG_PIXMAP_HFILL, 1, 17);

		farveMaxNum = AG_NumericalNew(tmpBox, AG_NUMERICAL_NO_HFILL, NULL, "");
		AG_WidgetBindDouble(farveMaxNum, "value", &farveMaxDbl);
		AG_SetEvent(farveMaxNum, "numerical-changed", genericPlotUpdate, "%p", this);
	}

	// Opret et pixmap vi kan tegne plottet i
	plotPix = AG_PixmapNew(plotBox, AG_PIXMAP_EXPAND, 250, 350);

	// Sæt op og vis vinduet
	AG_WindowSetPosition(vizWin, AG_WINDOW_CENTER, 1);
	AG_WindowShow(vizWin);
	return;
}

inline static void setPixmapColor(AG_Pixmap *pix, Uint32 col, int w, int h)
{
	AG_Surface *surf = AG_SurfaceStdRGB(w, h);	// Opret surface
	AG_Rect r = AG_RectFromSDL(&surf->clip_rect);
	AG_FillRect(surf, &r, col);					// Udfyld hele surface med én farve
	AG_PixmapReplaceCurrentSurface(pix, surf);	// Kopiér til pixmap'et, så den kan ses

	return;
}
